package Serveur;

import java.util.ArrayList;
import java.util.HashMap;

import Both.Trajet;
import Both.Ville;

/**
 * Cette classe permet d'effectuer des recherches pour aller d'une ville A à une
 * ville B utilisant une succession de transport
 * 
 * @author Jeremie
 * 
 */

public class RechercheTransport {

    Ville villeDepart;
    Ville villeArrive;

    Noeud origine;
    Noeud arrive;
    Noeud nActuel;
    Noeud newNoeudAjouter;

    Ville vActuelle;
    Trajet tActuel;

    ArrayList<Trajet> maList;
    ArrayList<Trajet> cheminSolution;

    ArrayList<Ville> listVilles;
    HashMap<Ville, Noeud> listNoeud;

    ArrayList<Noeud> ouverte;
    ArrayList<Noeud> ferme;
    boolean trouve;

    /**
     * Constructeur et initialisation du graphe
     * 
     * @param ville
     *            v1 ville de depart
     * @param ville
     *            v2 ville de d'arrive
     * @param listTrajet
     *            liste de tout les trajets existants
     */

    public RechercheTransport(Ville v1, Ville v2, ArrayList<Trajet> listTrajet)
            throws IllegalArgumentException {
        // verification des params
        if (v1 == null || v2 == null) {
            throw new IllegalArgumentException();
        }
        this.villeDepart = v1;
        this.villeArrive = v2;
        this.origine = new Noeud(v1, null, null);
        this.arrive = new Noeud(v2, null, null);
        maList = listTrajet;

        nActuel = origine;
        vActuelle = villeDepart;

        listVilles = new ArrayList<Ville>();
        listNoeud = new HashMap<Ville, Noeud>();

        ouverte = new ArrayList<Noeud>();
        ferme = new ArrayList<Noeud>();

        cheminSolution = new ArrayList<Trajet>();

        listVilles.add(vActuelle);
        listNoeud.put(vActuelle, nActuel);

        int j = 1;

        while (!maList.isEmpty()) {

            for (int i = 0; i < maList.size(); i++) {
                tActuel = maList.get(i);
                if (tActuel.getVilleDepart().equals(vActuelle)) {
                    if (!origine.villePresente(origine, tActuel
                            .getVilleArrive())) {
                        newNoeudAjouter = new Noeud(tActuel.getVilleArrive(),
                                tActuel, nActuel);
                        if (tActuel.getVilleArrive().equals(villeArrive)) {
                            arrive = newNoeudAjouter;
                        }
                        if (!listNoeud.containsKey(tActuel)) {
                            listVilles.add(tActuel.getVilleArrive());
                            listNoeud.put(tActuel.getVilleArrive(),
                                    newNoeudAjouter);
                        }
                        maList.remove(i);
                    } else {
                        nActuel.ajouterNoeud(origine.getNoeud(origine, tActuel
                                .getVilleArrive()), tActuel);
                        maList.remove(i);
                    }
                }
            }
            if (j <= listVilles.size()) {
                vActuelle = listVilles.get(j - 1);
                nActuel = listNoeud.get(vActuelle);
                j++;
            } else {
                break;
            }
        }
    }

    public ArrayList<Ville> getListVille() {
        return listVilles;
    }

    public HashMap<Ville, Noeud> getNoeud() {
        return listNoeud;
    }

    public boolean cheminPossible() {
        return listNoeud.containsKey(villeArrive);
    }

    /**
     * Rechercher le noeud le temp de trajet le plus faible
     * 
     * @return Noeud
     */
    public Noeud meilleur() {
        Noeud N_min = ouverte.get(0);
        Noeud temp;
        if (!N_min.equals(origine)) {
            for (int i = 1; i < ouverte.size(); i++) {
                temp = ouverte.get(i);
                if (temp.getTime() < N_min.getTime()) {
                    N_min = temp;
                }
            }
        }
        return N_min;
    }

    /**
     * Rechercher le chemin entre la ville A et B le plus court en question de
     * temp
     * 
     * @return
     */
    public void calculChemin() {
        trouve = false;
        ouverte.add(origine);
        int index;
        while (!ouverte.isEmpty() && !trouve) {

            nActuel = meilleur();
            ouverte.remove(ouverte.indexOf(nActuel));
            ferme.add(nActuel);

            if (nActuel.getVille().equals(arrive.getVille())) {
                // le chemin vers la destination a ete trouvé
                trouve = true;
            } else {
                ArrayList<Noeud> l = nActuel.getListNoeud();
                Noeud succ;
                for (int i = 0; i < l.size(); i++) {
                    // on va evaluer toute les destinations possible a partir du
                    // noeud actuel
                    succ = l.get(i);
                    succ.nbDeplacement = nActuel.nbDeplacement + 1;
                    succ.time = nActuel.getTime() + succ.getTime();
                    succ.parent = nActuel;
                    index = ferme.indexOf(succ);
                    if (index == -1) {
                        // succ non present dans la liste ferm ==> ajouté dans
                        // ouverte
                        ouverte.add(succ);
                    } else {
                        // succ trouve dans la list ferme
                        if (succ.getTime() < ferme.get(index).getTime()) {
                            // le cout du succ inf a celui dans la liste ferme
                            // ==> succ est ajouter list ouverte
                            ferme.remove(index);
                            ouverte.add(succ);
                        }
                    }
                }
            }
        }
    }

    /**
     * rend la liste des trajets a prendre par ordre pour aller de la ville A a
     * B
     * 
     * @return ArrayList<Trajet>
     */
    public ArrayList<Trajet> Chemin() {
        if (cheminPossible()) {
            // Calcul du chemin
            calculChemin();
            for (int i = 1; i < ferme.size(); i++) {
                cheminSolution.add(ferme.get(i).getTrajet(ferme.get(i-1)));
            }
        }
        return cheminSolution;

    }

}
